/**
 * @author harry
 * @date 2022/2/24/9:22
 */

package com.serviceOrchestration.interceptor;

import com.alibaba.fastjson.JSON;
import com.serviceOrchestration.annotation.RoleRestriction;
import com.serviceOrchestration.dao.RedisDao;
import com.serviceOrchestration.pojo.ReturnData;
import com.serviceOrchestration.util.JwtUtil;
import com.serviceOrchestration.util.StatusCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
    private static final String authorizationPattern = "\\s*Bearer\\s*\\S*\\s*";

    private RedisDao redisDao;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 没有匹配到路由，映射到/error，放行
        if(!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 2. HTTP方法为OPTIONS，放行
        String requestMethod = request.getMethod();
        if(HttpMethod.OPTIONS.matches(requestMethod)) {
            return true;
        }

        // 3. 检查url的权限限制
        Method handlerMethod = ((HandlerMethod) handler).getMethod();
        Class<?> handlerClass = handlerMethod.getDeclaringClass();
        List<String> rolesPermitted = new ArrayList<>();
        if(handlerMethod.isAnnotationPresent(RoleRestriction.class)) {
            String[] roles = handlerMethod.getAnnotation(RoleRestriction.class).value();
            rolesPermitted.addAll(Arrays.asList(roles));
        }
        if(handlerClass.isAnnotationPresent(RoleRestriction.class)) {
            String[] roles = handlerClass.getAnnotation(RoleRestriction.class).value();
            rolesPermitted.addAll(Arrays.asList(roles));
        }

        // 3.1 如果为空，则没有限制
        if(rolesPermitted.isEmpty()) {
            return true;
        }

        // 3.2 有role限制，验证token
        response.setStatus(401);// 默认设置unauthorized
        response.setContentType("application/json");
        String authorization = request.getHeader("Authorization");

        // 3.2.1 没有Authorization头，或者格式不对 "Bearer token"
        if(authorization == null || !authorization.matches(authorizationPattern)) {
            writeResponse(response, ReturnData.error(StatusCode.INVALIDATE_TOKEN));
            return false;
        }
        // 3.2.2 token格式校验不通过
        String token = authorization.trim().split("\\s+")[1];
        if (!JwtUtil.verifyToken(token)) {
            writeResponse(response, ReturnData.error(StatusCode.INVALIDATE_TOKEN));
            return false;
        }
        // 3.2.3 再看redis中有没有，如果是没过期但已经退出登录的账号token也要拦截
        injectRedisDao(request);
        String sub = JwtUtil.getSubjectFromToken(token);
        if(!redisDao.hasKey(RedisDao.TOKEN_KEY_PREFIX + sub)) {
            writeResponse(response, ReturnData.error(StatusCode.INVALIDATE_TOKEN));
            return false;
        }
        // 3.2.4 最后再看角色权限
        String role = JwtUtil.getRoleFromToken(token);
        if(rolesPermitted.contains(role)) {
            request.setAttribute("token", token);
            request.setAttribute("sub", sub);
            request.setAttribute("role", role);
            response.setStatus(200);
            return true;
        }
        // 4. 权限限制不通过
        writeResponse(response, ReturnData.error(StatusCode.NOT_PERMITTED));
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    }

    private void writeResponse(HttpServletResponse response, Object message) throws IOException {
        response.getWriter().write(JSON.toJSONString(message));
    }

    private void injectRedisDao(HttpServletRequest request) {
        if(redisDao == null) {
            BeanFactory beanFactory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
            redisDao = beanFactory.getBean(RedisDao.class);
        }
    }
}
